package xing2387.filtervideoplayer.filter;

import android.opengl.GLES20;
import android.util.Log;

import xing2387.filtervideoplayer.player.gl.FramebufferObject;

import static android.opengl.GLES20.GL_FLOAT;

/**
 * https://github.com/Jam3/glsl-fast-gaussian-blur
 */

public class GlFastGaussianBlurFilter extends GlFilter {

    private InnerFilter filterX = new InnerFilter(FRAGMENT_SHADER_BLUR_X);
    private InnerFilter filterY = new InnerFilter(FRAGMENT_SHADER_BLUR_Y);
    private CompositeFilter compositeFilter = new CompositeFilter();
    private FramebufferObject framebufferObject = new FramebufferObject();
    private FramebufferObject fbo2 = new FramebufferObject();
    private int renderInterval = 2;
    private int renderCount = 0;

    public GlFastGaussianBlurFilter() {

    }

    @Override
    public void setFrameSize(int width, int height) {
        int fboW = (int) (width * 0.5f);
        int fboH = (int) (height * 0.5f);
        super.setFrameSize(width, height);
        framebufferObject.setup(fboW, fboH);
        fbo2.setup(fboW, fboH);
        filterX.setFrameSize(width, height);
        filterY.setFrameSize(width, height);
        compositeFilter.setFrameSize(width, height);
    }

    @Override
    public void setVideoSize(int width, int height) {
        filterX.setVideoSize(width, height);
        filterY.setVideoSize(width, height);
        compositeFilter.setVideoSize(width, height);
    }

    @Override
    public void setup() {
        filterX.setup();
        filterY.setup();
        compositeFilter.setup();
    }

    @Override
    public void release() {
        filterX.release();
        filterY.release();
        compositeFilter.release();
    }

    private boolean skipDraw() {
        renderCount = renderCount % renderInterval;
        renderCount++;
        return renderCount != 1;
    }

    @Override
    public void draw(int texName, FramebufferObject fbo) {
//        filterY.setRadius(4.0f);
//        filterX.setRadius(4.0f);
//        framebufferObject.enable();
//        filterY.draw(texName, fbo);
////        filterY.draw(fbo2.getTexName(), fbo);
//        fbo2.enable();
//        filterX.draw(framebufferObject.getTexName(), fbo);
        if (!skipDraw()) {
            filterY.setRadius(3.0f);
            filterX.setRadius(3.0f);
            framebufferObject.enable();
            filterY.draw(texName, fbo);
//        filterY.draw(fbo2.getTexName(), fbo);
            fbo2.enable();
            filterX.draw(framebufferObject.getTexName(), fbo);
//
            filterY.setRadius(2.0f);
            filterX.setRadius(2.0f);
            framebufferObject.enable();
//        filterY.draw(texName, fbo);
            filterY.draw(fbo2.getTexName(), fbo);
            fbo2.enable();
            filterX.draw(framebufferObject.getTexName(), fbo);

            filterY.setRadius(1.0f);
            filterX.setRadius(1.0f);
            framebufferObject.enable();
//        filterY.draw(texName, fbo);
            filterY.draw(fbo2.getTexName(), fbo);
            fbo2.enable();
            filterX.draw(framebufferObject.getTexName(), fbo);
        }
        fbo.enable();
        compositeFilter.setSourceTexName(texName);
        compositeFilter.draw(fbo2.getTexName(), fbo);
    }

    private static final String BLUR_5 = "\n" +
            "vec4 blur5(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) {\n" +
            "  vec4 color = vec4(0.0);\n" +
            "  vec2 off1 = vec2(1.3333333333333333) * direction;\n" +
            "  color += texture2D(image, uv) * 0.29411764705882354;\n" +
            "  color += texture2D(image, uv + (off1 / resolution)) * 0.35294117647058826;\n" +
            "  color += texture2D(image, uv - (off1 / resolution)) * 0.35294117647058826;\n" +
            "  return color;\n" +
            "}";
    private static final String BLUR_9 = "\n" +
            "vec4 blur9(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) {\n" +
            "  vec4 color = vec4(0.0);\n" +
            "  vec2 off1 = vec2(1.3846153846) * direction;\n" +
            "  vec2 off2 = vec2(3.2307692308) * direction;\n" +
            "  color += texture2D(image, uv) * 0.2270270270;\n" +
            "  color += texture2D(image, uv + (off1 / resolution)) * 0.3162162162;\n" +
            "  color += texture2D(image, uv - (off1 / resolution)) * 0.3162162162;\n" +
            "  color += texture2D(image, uv + (off2 / resolution)) * 0.0702702703;\n" +
            "  color += texture2D(image, uv - (off2 / resolution)) * 0.0702702703;\n" +
            "  return color;\n" +
            "}";
    private static final String BLUR_13 = "\n" +
            "vec4 blur13(sampler2D image, vec2 uv, vec2 resolution, vec2 direction) {\n" +
            "   vec4 color = vec4(0.0);\n" +
            "   vec2 off1 = vec2(1.411764705882353) * direction;\n" +
            "   vec2 off2 = vec2(3.2941176470588234) * direction;\n" +
            "   vec2 off3 = vec2(5.176470588235294) * direction;\n" +
            "   color += texture2D(image, uv) * 0.1964825501511404;\n" +
            "   color += texture2D(image, uv + (off1 / resolution)) * 0.2969069646728344;\n" +
            "   color += texture2D(image, uv - (off1 / resolution)) * 0.2969069646728344;\n" +
            "   color += texture2D(image, uv + (off2 / resolution)) * 0.09447039785044732;\n" +
            "   color += texture2D(image, uv - (off2 / resolution)) * 0.09447039785044732;\n" +
            "   color += texture2D(image, uv + (off3 / resolution)) * 0.010381362401148057;\n" +
            "   color += texture2D(image, uv - (off3 / resolution)) * 0.010381362401148057;\n" +
            "   return color;\n" +
            " }\n";

    private static final String FRAGMENT_SHADER_BLUR_X =
            "precision mediump float;\n" +

                    "varying highp vec2 vTextureCoord;\n" +

                    "uniform lowp sampler2D sTexture\n;" +
                    "uniform float offsetY;\n" +
                    "uniform float offsetX;\n" +
                    "uniform float blurX;\n" +
                    "uniform float blurY;\n" +

                    "uniform float videoW;\n" +
                    "uniform float videoH;\n" +
                    "uniform float radius;\n" +
                    BLUR_5 +
                    "lowp vec4 blur(sampler2D image, vec2 uv, vec2 resolution) {\n" +
                    "    vec2 direction = vec2(radius, 0.0);\n" +
//                    "    return texture2D(image, uv);\n"+
                    "    return blur5(image, uv, resolution, direction);\n" +
                    "}" +

                    "void main() {\n" +
                    "   highp vec2 uv = vTextureCoord; \n" +
                    "   float inset = 0.02; \n" +
                    "   if(uv.x < offsetX + inset || uv.x > (1.0 - offsetX - inset) || uv.y < offsetY + inset || uv.y > (1.0 - offsetY - inset)) {\n" +
                    "           float u = blurX;\n" +
                    "           float v = blurY;\n" +
                    "           gl_FragColor = blur(sTexture, uv, vec2(videoW, videoH)); \n" +
                    "   } else {" +
                    "       gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);\n" +
                    "   }\n" +
                    "} \n";

    private static final String FRAGMENT_SHADER_BLUR_Y =
            "precision mediump float;\n" +

                    "varying highp vec2 vTextureCoord;\n" +

                    "uniform lowp sampler2D sTexture\n;" +
                    "uniform float offsetY;\n" +
                    "uniform float offsetX;\n" +
                    "uniform float blurX;\n" +
                    "uniform float blurY;\n" +

                    "uniform float videoW;\n" +
                    "uniform float videoH;\n" +
                    "uniform float radius;\n" +
                    BLUR_5 +
                    "lowp vec4 blur(sampler2D image, vec2 uv, vec2 resolution) {\n" +
                    "    vec2 direction = vec2(0.0, radius);\n" +
//                    "    return texture2D(image, uv);\n"+
                    "    return blur5(image, uv, resolution, direction);\n" +
                    "}" +

                    "void main() {\n" +
                    "   vec2 uv = vTextureCoord; \n" +
                    "   float inset = 0.02; \n" +
                    "   if(uv.x < offsetX + inset || uv.x > (1.0 - offsetX - inset) || uv.y < offsetY + inset || uv.y > (1.0 - offsetY - inset)) {\n" +
                    "       if (radius  > 2.5) { \n" +
                    "           float u = blurX + uv.x * (1.0 - blurX * 2.0);\n" +
                    "           float v = blurY + uv.y * (1.0 - blurY * 2.0);\n" +
                    "           gl_FragColor = blur(sTexture, vec2(u, v), vec2(videoW, videoH)); \n" +
                    "       } else { \n" +
                    "           gl_FragColor = blur(sTexture, uv, vec2(videoW, videoH)); \n" +
                    "       }\n" +
                    "   } else {\n" +
                    "       gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);\n" +
                    "   }\n" +
                    "} \n";


    private static class InnerFilter extends GlFilter {

        private static final String VERTEX_SHADER =
                "precision mediump float;\n" +
                        "attribute vec4 aPosition;" +
                        "attribute vec4 aTextureCoord;\n" +
                        "varying vec2 vTextureCoord;\n" +
                        "void main() {" +
                        "gl_Position = aPosition;" +
                        "vTextureCoord = vec2(aTextureCoord.x*2.0, 1.0 - aTextureCoord.y*2.0);" +
                        "}";
        private float offsetX = 0;
        private float offsetY = 0;

        private float blurOffsetX = 0;
        private float blurOffsetY = 0;

        private float scaledVideoW;
        private float scaledVideoH;

        private float radius = 1.0f;

        public InnerFilter(String fragmentShader) {
            super(VERTEX_SHADER, fragmentShader);
        }

        //mWidth surface宽度
        public void setTextureOffset(float x, float y) {
            this.offsetX = x;
            this.offsetY = y;
        }

        public void setBlurOffset(float x, float y) {
            this.blurOffsetX = x;
            this.blurOffsetY = y;
        }

        public void setRadius(float radius) {
            this.radius = radius;
        }

        @Override
        public void setFrameSize(int width, int height) {
            super.setFrameSize(width, height);
            adjustOffset();
        }

        @Override
        public void setVideoSize(int width, int height) {
            super.setVideoSize(width, height);
            adjustOffset();
        }

        private static final String TAG = "GlGaussianBlurFilter";

        public void adjustOffset() {
            if (mWidth == 0 || mHeight == 0 || videoH == 0 || videoW == 0) {
                return;
            }
            try {
                float offsetX = 0;
                float offsetY = 0;
                float blurX = 0;
                float blurY = 0;
                float scaledH = mHeight;
                float scaledW = mWidth;
                if (mWidth * videoH > videoW * mHeight) {       //surface宽高比 比 视频的大
                    offsetX = ((mWidth - (mHeight * videoW / (float) videoH)) / 2) / mWidth;
                    scaledH = (mWidth * videoH / (float) videoW);
                    blurY = (scaledH - mHeight) / (2 * scaledH);
                } else {
                    offsetY = ((mHeight - (mWidth * videoH / (float) videoW)) / 2) / mHeight;
                    scaledW = (mHeight * videoW / (float) videoH);
                    blurX = (scaledW - mWidth) / (2 * scaledW);
                }
                Log.d(TAG, "adjustOffset mWidth " + mWidth + " mHeight " +
                        mHeight + " videoW " + videoW + " videoH " + videoH +
                        " offsetX " + offsetX + " offsetY " + offsetY +
                        " blurX " + blurX + " blurY " + blurY);
                scaledVideoH = scaledH;
                scaledVideoW = scaledW;
                setTextureOffset(offsetX, offsetY);
                setBlurOffset(blurX, blurY);

            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onDraw() {
            GLES20.glUniform1f(getHandle("videoW"), mWidth / 4);
            GLES20.glUniform1f(getHandle("videoH"), mHeight / 4);
            GLES20.glUniform1f(getHandle("radius"), radius);

            GLES20.glUniform1f(getHandle("offsetX"), offsetX);
            GLES20.glUniform1f(getHandle("offsetY"), offsetY);
            GLES20.glUniform1f(getHandle("blurX"), blurOffsetX);
            GLES20.glUniform1f(getHandle("blurY"), blurOffsetY);
        }
    }

    private static class CompositeFilter extends GlFilter {

        private static final String VERTEX_SHADER =
                "attribute vec4 aPosition;" +
                        "attribute vec4 aTextureCoord;\n" +
                        "varying highp vec2 vTextureCoord;\n" +
                        "void main() {" +
                        "   gl_Position = aPosition;" +
                        "   vTextureCoord = vec2(aTextureCoord.x, aTextureCoord.y);" +
                        "}";

        private static final String FRAGMENT_SHADER = "" +
                "precision mediump float;\n" +
                "varying highp vec2 vTextureCoord;\n" +
                "uniform lowp sampler2D sTexture\n;" +
                "uniform lowp sampler2D sTexture2\n;" +
                "uniform float offsetY;\n" +
                "uniform float offsetX;\n" +
                "void main() {\n" +
                "   vec2 uv = vTextureCoord; \n" +
                "   if(uv.x < offsetX || uv.x > (1.0 - offsetX) || uv.y < offsetY || uv.y > (1.0 - offsetY)) {\n" +
                "       gl_FragColor = texture2D(sTexture, uv) / 3.0; \n" +
                "   } else {\n" +
                "       float u = (uv.x - offsetX) / (1.0 - offsetX * 2.0);\n" +
                "       float v = (uv.y - offsetY) / (1.0 - offsetY * 2.0);\n" +
                "       gl_FragColor = texture2D(sTexture2, vec2(u, v)); \n" +
                "   }\n" +
                "}\n";

        private float offsetX = 0;
        private float offsetY = 0;

        private float scaledVideoW;
        private float scaledVideoH;

        private int sourceTexName;

        public CompositeFilter() {
            super(VERTEX_SHADER, FRAGMENT_SHADER);
        }

        public void setSourceTexName(int sourceTexName) {
            this.sourceTexName = sourceTexName;
        }

        //mWidth surface宽度
        public void setTextureOffset(float x, float y) {
            this.offsetX = x;
            this.offsetY = y;
        }

        @Override
        public void setFrameSize(int width, int height) {
            super.setFrameSize(width, height);
            adjustOffset();
        }

        @Override
        public void setVideoSize(int width, int height) {
            super.setVideoSize(width, height);
            adjustOffset();
        }

        private static final String TAG = "GlGaussianBlurFilter";

        public void adjustOffset() {
            if (mWidth == 0 || mHeight == 0 || videoH == 0 || videoW == 0) {
                return;
            }
            try {
                float offsetX = 0;
                float offsetY = 0;
                float scaledH = mHeight;
                float scaledW = mWidth;
                if (mWidth * videoH > videoW * mHeight) {       //surface宽高比 比 视频的大
                    offsetX = ((mWidth - (mHeight * videoW / (float) videoH)) / 2) / mWidth;
                    scaledH = (mWidth * videoH / (float) videoW);
                } else {
                    offsetY = ((mHeight - (mWidth * videoH / (float) videoW)) / 2) / mHeight;
                    scaledW = (mHeight * videoW / (float) videoH);
                }
                Log.d(TAG, "adjustOffset mWidth " + mWidth + " mHeight " +
                        mHeight + " videoW " + videoW + " videoH " + videoH +
                        " offsetX " + offsetX + " offsetY " + offsetY);
                scaledVideoH = scaledH;
                scaledVideoW = scaledW;
                setTextureOffset(offsetX, offsetY);

            } catch (Exception e) {
                e.printStackTrace();
            }
        }


        public void draw(final int texName, final FramebufferObject fbo) {
            useProgram();

            GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, getVertexBufferName());
            GLES20.glEnableVertexAttribArray(getHandle("aPosition"));
            GLES20.glVertexAttribPointer(getHandle("aPosition"), VERTICES_DATA_POS_SIZE, GL_FLOAT, false, VERTICES_DATA_STRIDE_BYTES, VERTICES_DATA_POS_OFFSET);
            GLES20.glEnableVertexAttribArray(getHandle("aTextureCoord"));
            GLES20.glVertexAttribPointer(getHandle("aTextureCoord"), VERTICES_DATA_UV_SIZE, GL_FLOAT, false, VERTICES_DATA_STRIDE_BYTES, VERTICES_DATA_UV_OFFSET);

            GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texName);
            GLES20.glUniform1i(getHandle("sTexture"), 0);

            onDraw();

            GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);

            GLES20.glDisableVertexAttribArray(getHandle("aPosition"));
            GLES20.glDisableVertexAttribArray(getHandle("aTextureCoord"));
//            GLES20.glDisableVertexAttribArray(getHandle("aTextureCoord2"));
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
            GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, 0);
        }

        @Override
        public void onDraw() {
            GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
            GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, sourceTexName);
            GLES20.glUniform1i(getHandle("sTexture2"), 1);

            GLES20.glUniform1f(getHandle("offsetX"), offsetX);
            GLES20.glUniform1f(getHandle("offsetY"), offsetY);
        }
    }
}
